package com.ruoyi.trackapply;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.json.JSONUtil;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.ZipUtils;
import com.ruoyi.common.utils.biz.BizModule;
import com.ruoyi.common.utils.biz.PathUtil;
import com.ruoyi.common.utils.biz.ResultSaveModule;
import com.ruoyi.dm.mapper.DataManagementMapper;
import com.ruoyi.system.domain.ResultSave;
import com.ruoyi.system.service.IResultSaveService;
import com.ruoyi.trackapply.domain.*;
import common.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import plugins.LowAtmosphereCalcNative;
import plugins.OrbitBasicCalcNative;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 不通地磁水平条件下大气模型预报效应评估
 */

@Slf4j
@Service
public class ForcastWorkService {

    @Autowired
    private RedisTemplate redisTemplate;


    @Autowired
    private IResultSaveService resultSaveService;

    @Autowired
    private DataManagementMapper dataManagementMapper;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 评估
     * @param param
     * @return
     */
    public ForcastResVO estimate(ForcastWorkParam param) throws Exception {

        ForcastResVO resVO = new ForcastResVO();
        ForcastForceVO execRes = exec(param);

        Map<String, Object> x = new HashMap<>();
        Map<String, Object> y = new HashMap<>();
        Map<String, Object> z = new HashMap<>();
        Map<String, Object> vx = new HashMap<>();
        Map<String, Object> vy = new HashMap<>();
        Map<String, Object> vz = new HashMap<>();
        for (ForcastEstimateVO vo : execRes.getVos()) {
            String model = vo.getModel();
            x.put(model + "_x", vo.getX());
            y.put(model + "_y", vo.getY());
            z.put(model + "_z", vo.getZ());
            vx.put(model + "_vx", vo.getVx());
            vy.put(model + "_vy", vo.getVy());
            vz.put(model + "_vz", vo.getVz());
        }
        execRes.setX(x);
        execRes.setY(y);
        execRes.setZ(z);
        execRes.setVx(vx);
        execRes.setVy(vy);
        execRes.setVz(vz);
        resVO.setEstimate(execRes);
        //组装 地磁
        ForcastKpVO forcastKpVO = queryEarth(param);
        resVO.setKp(forcastKpVO);
        return resVO ;
    }

    private ForcastKpVO queryEarth(ForcastWorkParam param) {
        String startStr = DateUtil.format(param.getStartTime(), "yyyy-MM-dd HH:mm:ss");
        String endStr = DateUtil.format(param.getEndTime(), "yyyy-MM-dd HH:mm:ss");
        String sql = "select * from swdata_obs_kp where \"DATETIME\" between '"+startStr +"' and '" + endStr +"'";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        ForcastKpVO forcastKpVO = new ForcastKpVO();
        if (null != maps) {
            List<Date> times = new ArrayList<>();
            List<Double> kps = new ArrayList<>();
            for (Map<String, Object> map : maps) {
                times.add((Date) map.get("DATETIME"));
                kps.add((Double) map.get("KP"));
            }
            forcastKpVO.setTimes(times);
            forcastKpVO.setKps(kps);
        }
        return forcastKpVO;

    }


    /**
     * 结果入库
     * @param param
     */
    public void save(ForcastWorkParam param) {
        String key = MD5.create().digestHex(param.toString());
        String str = (String)redisTemplate.opsForValue().get(key);
        if (null == str) {
            exec(param);
            str =  (String) redisTemplate.opsForValue().get(key);
        }

        List<String> dataList = JSONUtil.toList(str, String.class);

        ResultSave entity = ResultSave.builder()
                .params("")
                .module(ResultSaveModule.TRACK_APPLY)
                .submodule(ResultSaveModule.TRACK_APPLY_INFLUENCE)
                .resultData(JSONUtil.toJsonStr(dataList))
                .startTime(DateUtil.format(param.getStartTime(),"yyyy-MM-dd HH:mm:ss"))
                .endTime(DateUtil.format(param.getEndTime(),"yyyy-MM-dd HH:mm:ss"))
                .createTime(new Date())
                .build();
        resultSaveService.resultSave(param,entity);
    }

    /**
     * 下载
     * @param param
     * @return
     */
    public String download(ForcastWorkParam param) throws IOException {
        String key = MD5.create().digestHex(param.toString());
        String str = (String)redisTemplate.opsForValue().get(key);
        if (null == str) {
            exec(param);
            str =  (String) redisTemplate.opsForValue().get(key);
        }

        List<String> dataList = JSONUtil.toList(str, String.class);
        String timeStr = DateUtil.format(new Date(), "yyyyMMddHHmmss");
        String zipPath = RuoYiConfig.getDownloadPath() + "/大气模式预报效应评估-"+timeStr+".zip";
        ZipUtils.zipWithPath(dataList,zipPath);
        return zipPath;
    }


    //执行评估
    private ForcastForceVO exec(ForcastWorkParam param) {

        /**
         *  *@paramin:  startEpoch          开始时间
         *  *@paramin:  endEpoch         结束时间
         *  *@paramin:  double dStep        时间间隔
         *  *@paramin:  StOrbitPoint initJ2kOp     初始轨道J2000位置速度
         *  *@paramin:  StAttributesForceModel  forceModelPara    力学模型参数
         *  *@paramin:  AtmosphereModeType AtmType 大气密度模型选择
         *  *@paramin:  outdatadir     输出日志路径
         *  *@paramin:  vector<StOrbitPoint>measureOpVec    实测J2000位置速度
         *  *@paramout: vector<StOrbitPoint> errOpVec   输出位置误差
         *  *@paramout: StPosErrorData outDensity 位置误差统计结果（最大值 最小值 均值 均方差）
         */

        String startStr = DateUtil.format(param.getStartTime(), "yyyy-MM-dd HH:mm:ss");
        String endStr = DateUtil.format(param.getEndTime(), "yyyy-MM-dd HH:mm:ss");

        //默认 位置速度 ....如果是初始轨道,需要 位置速度转一下
        StOrbitPoint initPoint = null;
        if (null != param.getBanChangZhou() && null != param.getPianXinLv()) {
            StKepler stKepler = new StKepler();
            stKepler.setEpoch(DateUtil.format(param.getEpochDate(), "yyyy-MM-dd HH:mm:ss"));
            stKepler.setSemiMajorAxis(param.getBanChangZhou());
            stKepler.setEccentricity(param.getPianXinLv());
            stKepler.setInclination(param.getQingJiao());
            stKepler.setRaan(param.getChiJing());
            stKepler.setArgOfPerigee(param.getJinDiDian());
            stKepler.setMeanAnomaly(param.getPingJinDiDian());
            initPoint = OrbitBasicCalcNative.calKeplerToCartesianInterfer(stKepler);
        }else {
            initPoint = new StOrbitPoint();
            initPoint.setStrEpoch(DateUtil.format(param.getEpochDate(), "yyyy-MM-dd HH:mm:ss"));
            initPoint.setDx(param.getX());
            initPoint.setDy(param.getY());
            initPoint.setDz(param.getZ());
            initPoint.setDvx(param.getVx());
            initPoint.setDvy(param.getVy());
            initPoint.setDvz(param.getVz());
        }


        //力学模型
        ForcastForceModel forceModel = param.getForceModel();
        StAttributesForceModel forceParam=new StAttributesForceModel();
        forceParam.nMaxDegree = forceModel.getMaxNum();
        forceParam.nMaxOrder = forceModel.getMaxStep();
        forceParam.bUseSolidTides = false;
        forceParam.bUseOceanTides = false;
        if ("solid".equals( forceModel.getForceType())) {
            forceParam.bUseSolidTides = true;
        }else if ("sea".equals(forceModel.getForceType())) {
            forceParam.bUseOceanTides = true;
        }

        // 阻力
        if (null != forceModel.getWindArea() && null !=forceModel.getForceIndex()  && null != forceModel.getMass() ) {
            forceParam.bDragUse = true;
            forceParam.dDragCd = forceModel.getForceIndex();
            forceParam.dDragArea = forceModel.getWindArea();
            forceParam.dDragMass = forceModel.getMass();
        }else {
            forceParam.bDragUse = false;
        }
        //太阳辐射压
        if (null != forceModel.getSunIndex() && null != forceModel.getSunArea()) {
            forceParam.bSolarPressureUse = true;
            forceParam.dCr = forceModel.getSunIndex();
            forceParam.dSolarPressureArea = forceModel.getSunArea();
            //    @ApiModelProperty("阴影模型  none 无;cylindrical 圆柱;cone 锥形;triangle 三角形")
            //    private String shadowModel;
             if ("cylindrical".equals(forceModel.getShadowModel())) {
                forceParam.eShadowModel = EForceModelShadowModel.EForceModelShadowModel_Cylindrical;
            }else if ("cone".equals(forceModel.getShadowModel())) {
                forceParam.eShadowModel = EForceModelShadowModel.EForceModelShadowModel_Cone;
            }else {
                //默认无
                forceParam.eShadowModel = EForceModelShadowModel.EForceModelShadowModel_None;
            }

             forceParam.dDailyF = forceModel.getDailyF07();
             forceParam.dAverageF = forceModel.getAvgF07();
             forceParam.dGeomagneticIndex = forceModel.getEarthIndex();
        }else {
            forceParam.bSolarPressureUse = false;
        }

        //三体引力
        ArrayList<Boolean> listThirdBodyGravity = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            listThirdBodyGravity.add(false);
        }

        for (String plant : forceModel.getPlants()) {
            //    @ApiModelProperty("三体引力 sun 太阳;jupiter 木星;saturn 土星;mercury 水星;
            //    neptune 海王星;moon 月球;venus 金星;mars 火星;uranus天王星;pluto 冥王星")
            switch (plant.toLowerCase()){
                case "mercury":listThirdBodyGravity.set(0,true);break;
                case "venus":listThirdBodyGravity.set(1,true);break;
                case "mars":listThirdBodyGravity.set(2,true);break;
                case "jupiter":listThirdBodyGravity.set(3,true);break;
                case "saturn":listThirdBodyGravity.set(4,true);break;
                case "uranus":listThirdBodyGravity.set(5,true);break;
                case "neptune":listThirdBodyGravity.set(6,true);break;
                case "pluto":listThirdBodyGravity.set(7,true);break;
                case "sun":listThirdBodyGravity.set(8,true);break;
                case "moon":listThirdBodyGravity.set(9,true);break;
            }
        }
        //日志
        String baseDir = PathUtil.tmpModuleDir(BizModule.TRACK_APPLY)
                + "/log/" + DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);
        new File(baseDir).mkdirs();
        String uuid = IdUtil.simpleUUID();
        String logFilePath = baseDir + "/" + uuid + ".log";
        //观测值 从gps 轨道数据获取
        String tableName = "collect_gnss_"+param.getSatellite().toLowerCase();
        List<StOrbitPoint> satOrbitDataLst = dataManagementMapper.getGNSSData(tableName, startStr, endStr)
                .stream()
                .map(map -> {
                    return new StOrbitPoint( //String epoch, double x, double y, double z, double vx, double vy, double vz
                            // POSITION_X,POSITION_Y,POSITION_Z,SPEED_VX,SPEED_VY,SPEED_VZ
                            DateUtil.formatDateTime((Date) map.get("obs_time")),
                            Double.parseDouble(map.get("position_x").toString()),
                            Double.parseDouble(map.get("position_y").toString()),
                            Double.parseDouble(map.get("position_z").toString()),
                            Double.parseDouble(map.get("speed_vx").toString()),
                            Double.parseDouble(map.get("speed_vx").toString()),
                            Double.parseDouble(map.get("speed_vx").toString())
                    );
                }).collect(Collectors.toList());
        if (CollUtil.isEmpty(satOrbitDataLst)){
            throw new ServiceException("未查询到卫星轨道数据");
        }


        //按不通模式 调用
        //大气模型
        //NRLMSISE-00
        //Jacchia71
        //JB2008
        //DTM2020
        EAtmosphereModeType spaceModel = null;
        String[] models = param.getModel().split(",");
        ForcastForceVO resVo = new ForcastForceVO();
        List<ForcastEstimateVO> voList = new ArrayList<>();
        //临时，保存文件至redis
        List<String> tmpList = new ArrayList<>();

        String key = MD5.create().digestHex(param.toString());
        Date now = new Date();
        for (String model : models) {
            switch (model) {
                case "NRLMSISE-00":spaceModel = EAtmosphereModeType.EAstronomicalType_NRLMSISE00;break;
                case "HarrisPriester":spaceModel = EAtmosphereModeType.EAstronomicalType_HarrisPriester;break;
                case "JB2008":spaceModel = EAtmosphereModeType.EAstronomicalType_JB2008;break;
                case "DTM2020":spaceModel = EAtmosphereModeType.EAstronomicalType_DTM2020;break;
            }

            List<StOrbitPoint> oSubCVec=new ArrayList<>();
            List<StOrbitPoint> staticData=new ArrayList<>();
            LowAtmosphereCalcNative.HpodOrbitPredictiontoCalEphAtmosphere(
                    startStr,endStr,Double.valueOf(param.getTimeGap()),initPoint,forceParam,spaceModel,logFilePath,
                    satOrbitDataLst,oSubCVec,staticData);

            if (CollectionUtil.isEmpty(oSubCVec)) {
                throw new ServiceException("计算结果为空");
            }
            log.info("oSubCVec size:{}",oSubCVec.size());

            List<String> obs=new ArrayList<>(oSubCVec.size());
            obs.add("TIME,X,Y,Z,VX,VY,VZ");
            //2021-10-12 11:59:47.000 -2410.8506303103 4970.1090172113 -747.8227813291 -3.9970488992 2.4005200638 -6.4334849224
            for (StOrbitPoint point : oSubCVec) {
                obs.add(point.toSTKString());
            }
            String txtPath = baseDir + "/"+model+"_" + DateUtil.format(now, DatePattern.PURE_DATETIME_PATTERN) + ".txt";
            FileUtil.writeLines(obs, txtPath,"utf-8");
            tmpList.add(txtPath);
            log.info("staticData size:{}",staticData.size());

            //组装结果
            ForcastEstimateVO forcastEstimateVO = makeupVO(model, oSubCVec,resVo);
            voList.add(forcastEstimateVO);

        }
        resVo.setVos(voList);
        redisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(tmpList),1, TimeUnit.HOURS);
        return resVo;
    }

    public  ForcastEstimateVO makeupVO(String model, List<StOrbitPoint> execRes, ForcastForceVO resVo) {
        boolean needTime = false;
        if (null == resVo.getTimes()) {
            needTime = true;
        }

        //组装 评估结果折线图
        List<String> times = new ArrayList<>(execRes.size());
        List<Double> xs = new ArrayList<>(execRes.size());
        List<Double> ys = new ArrayList<>(execRes.size());
        List<Double> zs = new ArrayList<>(execRes.size());
        List<Double> vxs = new ArrayList<>(execRes.size());
        List<Double> vys = new ArrayList<>(execRes.size());
        List<Double> vzs = new ArrayList<>(execRes.size());
        for (StOrbitPoint one : execRes) {
            if (needTime) {
                times.add(one.getStrEpoch());
            }
            xs.add(one.getDz());
            ys.add(one.getDy());
            zs.add(one.getDz());
            vxs.add(one.getDvx());
            vys.add(one.getDvy());
            vzs.add(one.getDvz());
        }
        ForcastEstimateVO vo = new ForcastEstimateVO();
        vo.setModel(model);
        vo.setX(xs);
        vo.setY(ys);
        vo.setZ(zs);
        vo.setVx(vxs);
        vo.setVy(vys);
        vo.setVz(vzs);
        //填充时间
        if (needTime) {
            resVo.setTimes(times);
        }

        return vo;
    }
}
